@@ -1,6 +1,7 @@ |
||
1 | 1 |
source 'https://rubygems.org' |
2 | 2 |
|
3 | 3 |
gem 'rails' |
4 |
+gem 'rake' |
|
4 | 5 |
gem 'mysql2' |
5 | 6 |
gem 'devise' |
6 | 7 |
gem 'rails_admin' |
@@ -0,0 +1,39 @@ |
||
1 |
+# This controller is designed to allow your Agents to receive cross-site Webhooks (posts). When POSTed, your Agent will |
|
2 |
+# have #receive_webhook called on itself with the POST params. |
|
3 |
+# |
|
4 |
+# Make POSTs to the following URL: |
|
5 |
+# http://yourserver.com/users/:user_id/webhooks/:agent_id/:secret |
|
6 |
+# where :user_id is your User's id, :agent_id is an Agent's id, and :secret is a token that should be |
|
7 |
+# user-specifiable in your Agent. It is highly recommended that you verify this token whenever #receive_webhook |
|
8 |
+# is called. For example, one of your Agent's options could be :secret and you could compare this value |
|
9 |
+# to params[:secret] whenever #receive_webhook is called on your Agent, rejecting invalid requests. |
|
10 |
+# |
|
11 |
+# Your Agent's #receive_webhook method should return an Array of [json_or_string_response, status_code]. For example: |
|
12 |
+# [{status: "success"}, 200] |
|
13 |
+# or |
|
14 |
+# ["not found", 404] |
|
15 |
+ |
|
16 |
+class WebhooksController < ApplicationController |
|
17 |
+ skip_before_filter :authenticate_user! |
|
18 |
+ |
|
19 |
+ def create |
|
20 |
+ user = User.find_by_id(params[:user_id]) |
|
21 |
+ if user |
|
22 |
+ agent = user.agents.find_by_id(params[:agent_id]) |
|
23 |
+ if agent |
|
24 |
+ response, status = agent.trigger_webhook(params.except(:action, :controller, :agent_id, :user_id)) |
|
25 |
+ if response.is_a?(String) |
|
26 |
+ render :text => response, :status => status || 200 |
|
27 |
+ elsif response.is_a?(Hash) |
|
28 |
+ render :json => response, :status => status || 200 |
|
29 |
+ else |
|
30 |
+ head :ok |
|
31 |
+ end |
|
32 |
+ else |
|
33 |
+ render :text => "agent not found", :status => :not_found |
|
34 |
+ end |
|
35 |
+ else |
|
36 |
+ render :text => "user not found", :status => :not_found |
|
37 |
+ end |
|
38 |
+ end |
|
39 |
+end |
@@ -60,6 +60,11 @@ class Agent < ActiveRecord::Base |
||
60 | 60 |
# Implement me in your subclass of Agent. |
61 | 61 |
end |
62 | 62 |
|
63 |
+ def receive_webhook(params) |
|
64 |
+ # Implement me in your subclass of Agent. |
|
65 |
+ ["not implemented", 404] |
|
66 |
+ end |
|
67 |
+ |
|
63 | 68 |
# Implement me in your subclass to decide if your Agent is working. |
64 | 69 |
def working? |
65 | 70 |
raise "Implement me in your subclass" |
@@ -88,6 +93,13 @@ class Agent < ActiveRecord::Base |
||
88 | 93 |
message.gsub(/<([^>]+)>/) { Utils.value_at(payload, $1) || "??" } |
89 | 94 |
end |
90 | 95 |
|
96 |
+ def trigger_webhook(params) |
|
97 |
+ receive_webhook(params).tap do |
|
98 |
+ self.last_webhook_at = Time.now |
|
99 |
+ save! |
|
100 |
+ end |
|
101 |
+ end |
|
102 |
+ |
|
91 | 103 |
def set_default_schedule |
92 | 104 |
self.schedule = default_schedule unless schedule.present? || cannot_be_scheduled? |
93 | 105 |
end |
@@ -16,6 +16,7 @@ Huginn::Application.routes.draw do |
||
16 | 16 |
match "/worker_status" => "worker_status#show" |
17 | 17 |
|
18 | 18 |
post "/users/:user_id/update_location/:secret" => "user_location_updates#create" |
19 |
+ post "/users/:user_id/webhooks/:agent_id/:secret" => "webhooks#create" |
|
19 | 20 |
|
20 | 21 |
mount RailsAdmin::Engine => '/admin', :as => 'rails_admin' |
21 | 22 |
# match "/delayed_job" => DelayedJobWeb, :anchor => false |
@@ -0,0 +1,5 @@ |
||
1 |
+class AddLastWebhookAtToAgents < ActiveRecord::Migration |
|
2 |
+ def change |
|
3 |
+ add_column :agents, :last_webhook_at, :datetime |
|
4 |
+ end |
|
5 |
+end |
@@ -11,7 +11,7 @@ |
||
11 | 11 |
# |
12 | 12 |
# It's strongly recommended to check this file into your version control system. |
13 | 13 |
|
14 |
-ActiveRecord::Schema.define(:version => 20130126080736) do |
|
14 |
+ActiveRecord::Schema.define(:version => 20130509053743) do |
|
15 | 15 |
|
16 | 16 |
create_table "agents", :force => true do |t| |
17 | 17 |
t.integer "user_id" |
@@ -26,6 +26,7 @@ ActiveRecord::Schema.define(:version => 20130126080736) do |
||
26 | 26 |
t.datetime "created_at", :null => false |
27 | 27 |
t.datetime "updated_at", :null => false |
28 | 28 |
t.text "memory", :limit => 2147483647 |
29 |
+ t.datetime "last_webhook_at" |
|
29 | 30 |
end |
30 | 31 |
|
31 | 32 |
add_index "agents", ["schedule"], :name => "index_agents_on_schedule" |
@@ -0,0 +1,54 @@ |
||
1 |
+require 'spec_helper' |
|
2 |
+ |
|
3 |
+describe WebhooksController do |
|
4 |
+ class Agents::WebhookReceiverAgent < Agent |
|
5 |
+ cannot_receive_events! |
|
6 |
+ cannot_be_scheduled! |
|
7 |
+ |
|
8 |
+ def receive_webhook(params) |
|
9 |
+ if params.delete(:secret) == options[:secret] |
|
10 |
+ memory[:webhook_values] = params |
|
11 |
+ ["success", 200] |
|
12 |
+ else |
|
13 |
+ ["failure", 404] |
|
14 |
+ end |
|
15 |
+ end |
|
16 |
+ end |
|
17 |
+ |
|
18 |
+ before do |
|
19 |
+ stub(Agents::WebhookReceiverAgent).valid_type?("Agents::WebhookReceiverAgent") { true } |
|
20 |
+ @agent = Agents::WebhookReceiverAgent.new(:name => "something", :options => { :secret => "my_secret" }) |
|
21 |
+ @agent.user = users(:bob) |
|
22 |
+ @agent.save! |
|
23 |
+ end |
|
24 |
+ |
|
25 |
+ it "should not require login to trigger a webhook" do |
|
26 |
+ @agent.last_webhook_at.should be_nil |
|
27 |
+ post :create, :user_id => users(:bob).to_param, :agent_id => @agent.id, :secret => "my_secret", :key => "value", :another_key => "5" |
|
28 |
+ @agent.reload.last_webhook_at.should be_within(2).of(Time.now) |
|
29 |
+ response.body.should == "success" |
|
30 |
+ response.should be_success |
|
31 |
+ end |
|
32 |
+ |
|
33 |
+ it "should call receive_webhook" do |
|
34 |
+ post :create, :user_id => users(:bob).to_param, :agent_id => @agent.id, :secret => "my_secret", :key => "value", :another_key => "5" |
|
35 |
+ @agent.reload.memory[:webhook_values].should == { :key => "value", :another_key => "5" } |
|
36 |
+ response.body.should == "success" |
|
37 |
+ response.should be_success |
|
38 |
+ |
|
39 |
+ post :create, :user_id => users(:bob).to_param, :agent_id => @agent.id, :secret => "not_my_secret", :no => "go" |
|
40 |
+ @agent.reload.memory[:webhook_values].should_not == { :no => "go" } |
|
41 |
+ response.body.should == "failure" |
|
42 |
+ response.should be_missing |
|
43 |
+ end |
|
44 |
+ |
|
45 |
+ it "should fail on incorrect users" do |
|
46 |
+ post :create, :user_id => users(:jane).to_param, :agent_id => @agent.id, :secret => "my_secret", :no => "go" |
|
47 |
+ response.should be_missing |
|
48 |
+ end |
|
49 |
+ |
|
50 |
+ it "should fail on incorrect agents" do |
|
51 |
+ post :create, :user_id => users(:bob).to_param, :agent_id => 454545, :secret => "my_secret", :no => "go" |
|
52 |
+ response.should be_missing |
|
53 |
+ end |
|
54 |
+end |
@@ -49,7 +49,7 @@ describe Agent do |
||
49 | 49 |
end |
50 | 50 |
end |
51 | 51 |
|
52 |
- describe "with a mock source" do |
|
52 |
+ describe "with an example Agent" do |
|
53 | 53 |
class Agents::SomethingSource < Agent |
54 | 54 |
default_schedule "2pm" |
55 | 55 |
|